/*
 * Copyright (C) 2004 Johan Maasing johan at zoom.nu Licensed under the Apache
 * License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
 * or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package nu.zoom.swing.desktop.workbench;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JMenuBar;
import javax.swing.KeyStroke;

import nu.zoom.swing.desktop.CloseVetoListener;
import nu.zoom.swing.desktop.ErrorReporter;
import nu.zoom.swing.desktop.PlugIn;
import nu.zoom.swing.desktop.WorkbenchFrame;
import nu.zoom.swing.desktop.WorkbenchListener;
import nu.zoom.swing.desktop.WorkbenchMenuBar;
import nu.zoom.swing.desktop.common.BackendException;
import nu.zoom.swing.desktop.frame.Desktop;
import nu.zoom.swing.desktop.preferences.Preferences;
import nu.zoom.swing.desktop.statusbar.StatusBar;
import nu.zoom.swing.desktop.statusbar.impl.StatusBarImpl;
import nu.zoom.swing.desktop.worker.WorkerFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.ops4j.gaderian.Messages;

/**
 * Implementation of the workbench interface.
 */
public class WorkbenchImpl implements nu.zoom.swing.desktop.Workbench {

	private Log log = LogFactory.getLog(getClass());
	private static final String PREFS_POSITION_X = "workbench.posx";
	private static final String PREFS_POSITION_Y = "workbench.posy";
	private static final String PREFS_WIDTH = "workbench.width";
	private static final String PREFS_HEIGHT = "workbench.height";
	private JFrame mainFrame;
	private WorkbenchMenuBarImpl workbenchMenuBar;
	private Preferences preferences;
	private Desktop desktop;
	private boolean restarting = false;
	private boolean closing = false;
	private boolean starting = false;
	private boolean listenersStarting = false;
	private Messages messages;
	private final ArrayList<WorkbenchListener> workbenchListeners = new ArrayList<WorkbenchListener>();
	private final ArrayList<CloseVetoListener> closeVetoListeners = new ArrayList<CloseVetoListener>();
	private PlugIn lafManager;
	private List<PlugIn> pluginList;
	private StatusBar statusBar;

	public WorkbenchImpl(Preferences preferences, PlugIn lafManager, Messages messages, List<PlugIn> pluginList) {
		super();
		this.preferences = preferences;
		this.messages = messages;
		this.lafManager = lafManager;
		this.pluginList = pluginList;
	}

	/**
	 * @param messages
	 *            The messages to set.
	 */
	public void setMessages(Messages messages) {
		this.messages = messages;
	}

	@Override
	public synchronized void start() {
		log.trace("Trying to start workbench");
		if (starting) {
			log.info("Workbench is already starting");
			// Already starting in another thread
			return;
		}
		starting = true;
		try {
			log.trace("Initializing LAF manager");
			// TODO LafManager should be split into two parts, the initialize
			// method is called to late since the GUI elements have been
			// constructed. It should have a pre-start hook.
			lafManager.initialize();
		} catch (Exception exc) {
			System.err.println(exc);
			System.exit(-1);
		}
		if (!EventQueue.isDispatchThread()) {
			log.trace("Not running in EventQueue thread, dispatching workbench start to EventQueue");
			EventQueue.invokeLater(new Runnable() {

				@Override
				public void run() {
					startOnEventQueue();
				}
			});
		} else {
			startOnEventQueue();
		}
	}

	private synchronized void startOnEventQueue() {
		workbenchMenuBar = new WorkbenchMenuBarImpl(this, messages);
		desktop = new Desktop(messages, workbenchMenuBar, preferences, this);

		// Position main window on the screen
		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
		int width = 1024; // Default if the preferences does not say otherwise
		int height = 768; // Default if the preferences does not say otherwise
		int posx = (screenSize.width / 2) - (width / 2); // Default if the
		// preferences does
		// not say otherwise
		int posy = (screenSize.height / 2) - (height / 2); // Default if the
		// preferences does
		// not say otherwise
		try {
			Integer prefPosX = preferences.getInteger(PREFS_POSITION_X);
			Integer prefPosY = preferences.getInteger(PREFS_POSITION_Y);
			Integer prefWidth = preferences.getInteger(PREFS_WIDTH);
			Integer prefHeight = preferences.getInteger(PREFS_HEIGHT);
			posx = (prefPosX != null) ? prefPosX.intValue() : posx;
			posy = (prefPosY != null) ? prefPosY.intValue() : posy;
			width = (prefWidth != null) ? prefWidth.intValue() : width;
			height = (prefHeight != null) ? prefHeight.intValue() : height;
		} catch (Exception exc) {
			log.error("Unable to retrieve workbench position and size from preferences", exc);
		}
		if (posx < 0) {
			log.trace("Position outside screen, clamping");
			posx = 0;
		} else if (posx > screenSize.width) {
			log.trace("Position outside screen, clamping");
			posx = 0;
		}
		if (posy < 0) {
			log.trace("Position outside screen, clamping");
			posy = 0;
		} else if (posy > height) {
			log.trace("Position outside screen, clamping");
			posy = 0;
		}
		if ((posx + width) > screenSize.width) {
			log.trace("Width outside screen, clamping");
			width = screenSize.width - posx;
		}
		if ((posy + height) > screenSize.height) {
			log.trace("Height outside screen, clamping");
			height = screenSize.height - posy;
		}
		log.trace("Workbench position: x=" + posx + " y=" + posy + " width=" + width + " height=" + height);
		String title = "";
		title = messages.getMessage("window.title.workbench");

		final StatusBarImpl statusBarImpl = new StatusBarImpl();
		this.statusBar = WorkerFactory.decorate(statusBarImpl, StatusBar.class);

		mainFrame = new JFrame(title);
		mainFrame.setJMenuBar(workbenchMenuBar.getJMenuBar());
		mainFrame.getContentPane().setLayout(new BorderLayout());
		mainFrame.getContentPane().add(desktop.getDesktopPane(), BorderLayout.CENTER);
		mainFrame.getContentPane().add(statusBarImpl, BorderLayout.SOUTH);
		mainFrame.setLocation(posx, posy);
		mainFrame.setSize(width, height);
		mainFrame.validate();
		mainFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
		mainFrame.addWindowListener(new WindowAdapter() {

			@Override
			public void windowClosing(WindowEvent e) {
				close();
			}
		});

		// Do not re-initialize the plug ins if we are restarting
		if (!restarting) {
			log.trace("Initializing workbench plug-ins");
			for (PlugIn plugIn : pluginList) {
				try {
					log.trace("Initializing plugin: " + plugIn);
					plugIn.initialize();
				} catch (Exception exc) {
					log.error("Unable to initialize plug-in", exc);
				}
			}
		}

		fireStart();
		mainFrame.setVisible(true);
		starting = false;
		EventQueue.invokeLater(new Runnable() {

			@Override
			public void run() {
				fireVisible();
			}
		});
	}

	private synchronized void fireVisible() {
		for (WorkbenchListener listener : workbenchListeners) {
			listener.visible();
		}
	}

	private synchronized void fireStart() {
		listenersStarting = true;
		for (WorkbenchListener listener : workbenchListeners) {
			listener.start();
		}
		listenersStarting = false;
	}

	@Override
	public synchronized WorkbenchFrame createWorkbenchFrame(final String preferencesKey, final JComponent content,
			final JMenuBar menu, final boolean resizable, final boolean maximizable, final boolean detached) {
		log.trace("Creating workbench frame instance");
		WorkbenchFrame wframe = desktop.createFrame(preferencesKey, content, menu, resizable, maximizable, detached);
		return wframe;
	}

	@Override
	public synchronized WorkbenchFrame createWorkbenchFrame(final String preferencesKey, final JComponent content,
			final JMenuBar menu, final boolean resizable, final boolean maximizable) {
		log.trace("Creating workbench frame instance");
		WorkbenchFrame wframe = desktop.createFrame(preferencesKey, content, menu, resizable, maximizable, null);
		return wframe;
	}

	@Override
	public Frame getDialogOwner() {
		return mainFrame;
	}

	@Override
	public synchronized void close() {
		log.trace("Trying to close workbench");
		if (mainFrame == null) {
			log.fatal("Workbench not started, unable to stop");
			throw new IllegalStateException("Workbench must be started before you can close it");
		}
		if (closing) {
			log.trace("Workbench already stopping");
			// Already closing because close was already called (in another
			// thread)
			return;
		}
		closing = true;
		log.trace("Checking if any registered listeners veto the close");
		// Check of any listeners veto the close operation
		for (CloseVetoListener listener : closeVetoListeners) {
			if (!listener.canClose()) {
				log.trace("Listener veto'ed the close operation, aborting close");
				closing = false;
				return;
			}
		}

		// inform listeners that we are closing. Do this before closing the
		// frames so that listeners have a chance to save layout preferences.
		log.trace("Informing listeners that workbench is closing");
		for (WorkbenchListener listener : workbenchListeners) {
			listener.close();
		}
		if (!restarting) {
			log.trace("Dropping references to listeners");
			// If we restart we keep all the listeners so that they can be
			// informed of the start
			workbenchListeners.clear();
			closeVetoListeners.clear();
		}

		// Close the workbench frames
		log.trace("Close all frames");
		desktop.closeAll();

		// Save the workbench frame preferences
		Point location = mainFrame.getLocation();
		Dimension size = mainFrame.getSize();
		if (log.isTraceEnabled()) {
			log.trace("Trying to save workbench position: " + location + " and size " + size);
		}
		try {
			preferences.setInteger(PREFS_POSITION_X, new Integer(location.x));
			preferences.setInteger(PREFS_POSITION_Y, new Integer(location.y));
			preferences.setInteger(PREFS_WIDTH, new Integer(size.width));
			preferences.setInteger(PREFS_HEIGHT, new Integer(size.height));
		} catch (BackendException exc) {
			log.error("Unable to saze workbench location and size to preferences", exc);
		}
		log.trace("Disposing of main frame");
		mainFrame.dispose();
		closing = false;
	}

	@Override
	public synchronized void restartGUI() {
		log.trace("Trying to restart workbench");
		// Already in the process of restarting.
		if (restarting) {
			log.trace("Workbench is already restarting");
			return;
		}
		restarting = true;
		close();
		startOnEventQueue();
		restarting = false;
	}

	@Override
	public void setTitle(final String workbenchTitle) {
		if (mainFrame == null) {
			log.fatal("Workbench must be started before you can set a title");
			throw new IllegalStateException("Workbench must be started before you can set a title");
		}
		log.trace("Setting workbench title: " + workbenchTitle);
		mainFrame.setTitle(workbenchTitle);
	}

	@Override
	public void setIcon(final List<ImageIcon> imageIcons) {
		if (mainFrame == null) {
			log.fatal("Workbench must be started before you can set an icon");
			throw new IllegalStateException("Workbench must be started before you can set an icon");
		}
		log.trace("Setting workbench icon");
		ArrayList<Image> images = new ArrayList<Image>();
		if (imageIcons != null) {
			for (ImageIcon imageIcon : imageIcons) {
				images.add(imageIcon.getImage());
			}
		}
		mainFrame.setIconImages(images);
	}

	@Override
	public synchronized ErrorReporter getErrorReporter() {
		return new ErrorReporterImpl(this, messages);
	}

	@Override
	public synchronized void addWorkBenchListener(final WorkbenchListener listener) {
		if (listener != null) {
			if (listenersStarting) {
				EventQueue.invokeLater(new Runnable() {

					public void run() {
						workbenchListeners.add(listener);
					}
				});
			} else {
				workbenchListeners.add(listener);
			}
		}
	}

	@Override
	public synchronized void removeWorkBenchListener(WorkbenchListener listener) {
		workbenchListeners.remove(listener);
	}

	@Override
	public synchronized void addCloseVetoListener(CloseVetoListener listener) {
		if (listener != null) {
			closeVetoListeners.add(listener);
		}
	}

	@Override
	public synchronized void removeCloseVetoListener(CloseVetoListener listener) {
		closeVetoListeners.remove(listener);
	}

	@Override
	public WorkbenchMenuBar getMenuBar() {
		return workbenchMenuBar;
	}

	@Override
	public void registerKeyboardAction(ActionListener anAction, KeyStroke aKeyStroke, int aCondition) {
		desktop.registerKeyboardAction(anAction, aKeyStroke, aCondition);
	}

	@Override
	public void registerKeyboardAction(ActionListener anAction, String aCommand, KeyStroke aKeyStroke, int aCondition) {
		desktop.registerKeyboardAction(anAction, aCommand, aKeyStroke, aCondition);
	}

	@Override
	public StatusBar getStatusbar() {
		return this.statusBar;
	}
}
